/*
 * Animated.h
 *
 *  Created on: 2009/02/17
 *      Author: Yongshik Cho
 */

#ifndef ANIMATED_H_
#define ANIMATED_H_

#include <vector>
#include "core/Prerequisites.h"
#include "core/Quaternion.h"
#include "core/Math3D.h"
#include "Particle.h"



typedef std::pair<size_t, size_t> AnimRange;


template <class T>
class Identity
{
public:
	static const T& conv(const T& t)
	{
		return t;
	}
};

// In WoW 2.0+ Blizzard are now storing rotation data in 16bit values instead of 32bit.
// I don't really understand why as its only a very minor saving in model sizes and adds extra overhead in
// processing the models.  Need this structure to read the data into.
struct PACK_QUATERNION
{
public:
	int16 x, y, z, w;
};

class Quat16ToQuat32
{
public:
	static const Quaternion conv(const PACK_QUATERNION t)
	{
		return Quaternion(
			float(t.x < 0? t.x + 32768 : t.x - 32767)/ 32767.0f,
			float(t.y < 0? t.y + 32768 : t.y - 32767)/ 32767.0f,
			float(t.z < 0? t.z + 32768 : t.z - 32767)/ 32767.0f,
			float(t.w < 0? t.w + 32768 : t.w - 32767)/ 32767.0f);
	}
};

// Convert opacity values stored as shorts to floating point
// I wonder why Blizzard decided to save 2 bytes by doing this
class ShortToFloat
{
public:
	static const float conv(const short t)
	{
		return t/32767.0f;
	}
};

/*
	Generic animated value class:

	T is the data type to animate
	D is the data type stored in the file (by default this is the same as T)
	Conv is a conversion object that defines T conv(D) to convert from D to T
		(by default this is an identity function)
	(there might be a nicer way to do this? meh meh)
*/

template <class T, class D=T, class Conv=Identity<T> >
class Animated
{
public:
	T getValue(uint32 anim, uint32 time, int32 global_time = 0)
	{
		if(mType != INTERPOLATION_NONE || mData.size()>1)
		{
			AnimRange range;

			// obtain a time value and a data range
			if(mSeq>-1)
			{
				if (mGlobals[mSeq]==0)
					time = 0;
				else
					time = global_time % mGlobals[mSeq];

				range.first = 0;
				range.second = mData.size()-1;
			}
			else
			{
				range = mRanges[anim];
				time %= mTimes[mTimes.size()-1]; // I think this might not be necessary?
			}

 			if(range.first != range.second)
 			{
				size_t t1, t2;
				size_t pos=0;
				for (size_t i=range.first; i<range.second; i++)
				{
					if (time >= mTimes[i] && time < mTimes[i+1])
					{
						pos = i;
						break;
					}
				}

				t1 = mTimes[pos];
				t2 = mTimes[pos+1];
				float r = (time-t1)/(float)(t2-t1);

				if(mType == INTERPOLATION_LINEAR)
					return interpolate<T>(r, mData[pos], mData[pos+1]);
				else if(mType == INTERPOLATION_NONE)
					return mData[pos];
				else
				{
					// INTERPOLATION_HERMITE is only used in cameras afaik?
					return interpolateHermite<T>(r, mData[pos], mData[pos+1], mIn[pos], mOut[pos]);
				}
			}
 			else
			{
				return mData[range.first];
			}
		}
		else
		{
			// default value
			if(mData.size() == 0)
				return T();
			else
				return mData[0];
		}
	}

	void init(AnimationBlock &b, int32 *gs)
	{
		mGlobals = gs;
		mType = b.type;
		mSeq = b.seq;

		if(mSeq!=-1)
		{
            assert(gs);
		}

		// Old method
		//mUsed = (mType != INTERPOLATION_NONE) || (mSeq != -1);
		// New method suggested by Cryect
		mUsed = (b.keys.size() > 0);

		// ranges
		if(b.ranges.size() > 0)
		{
			for(size_t k=0; k<b.ranges.size();++k)
			{
				AnimRange r;
				r.first = b.ranges[k];
				r.second = b.ranges[k];
				mRanges.push_back(r);
			}
		}
		else if(mType!=0 && mSeq==-1)
		{
			AnimRange r;
			r.first = 0;
			r.second = b.keys.size() - 1;
			mRanges.push_back(r);
		}

		// times
		assert(b.times.size() == b.keys.size());
		for(size_t i=0; i<b.times.size(); i++)
			mTimes.push_back(b.times[i]);

		// keyframes
		switch(mType)
		{
		case INTERPOLATION_NONE:
		case INTERPOLATION_LINEAR:
			for(size_t i=0; i<b.keys.size(); i++)
				mData.push_back(Conv::conv((D)b.keys[i]));
			break;
		case INTERPOLATION_HERMITE:
			for(size_t i=0; i<b.keys.size(); i++)
			{
				mData.push_back(Conv::conv((D)b.keys[i*3]));
				mIn.push_back(Conv::conv((D)b.keys[i*3+1]));
				mOut.push_back(Conv::conv((D)b.keys[i*3+2]));
			}
			break;
		}

		if(mData.size()==0)
			mData.push_back(T());
	}

	void fix(T fixfunc(const T))
	{
		switch(mType)
		{
		case INTERPOLATION_NONE:
		case INTERPOLATION_LINEAR:
			for(size_t i=0; i<mData.size(); i++)
			{
				mData[i] = fixfunc(mData[i]);
			}
			break;
		case INTERPOLATION_HERMITE:
			for(size_t i=0; i<mData.size(); i++)
			{
				mData[i] = fixfunc(mData[i]);
				mIn[i] = fixfunc(mIn[i]);
				mOut[i] = fixfunc(mOut[i]);
			}
			break;
		}
	}

	inline const bool getUsed() const { return mUsed; }
	inline const int32 getType() const { return mType; }
	inline const int32 getSeq() const { return mSeq; }

protected:
	bool 				mUsed;
	int32 				mType, mSeq;
	int32*				mGlobals;

	vector<AnimRange> 	mRanges;
	vector<uint32> 		mTimes;
	vector<T> 			mData;
	// for nonlinear interpolations:
	vector<T> 			mIn, mOut;
};

typedef Animated<float, short, ShortToFloat> AnimatedShort;

#endif /* ANIMATED_H_ */
